Clover icon

compiler

  1. Project Clover database Mon Jan 2 2023 15:09:37 MST
  2. Package com.google.javascript.jscomp

File SimpleDefinitionFinder.java

 

Coverage histogram

../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

90
154
18
3
465
297
77
0.5
8.56
6
4.28

Classes

Class Line # Actions
SimpleDefinitionFinder 45 105 51 14
0.9217877492.2%
SimpleDefinitionFinder.DefinitionGatheringCallback 149 42 24 1
0.9863013698.6%
SimpleDefinitionFinder.UseSiteGatheringCallback 271 7 2 0
1.0100%
 

Contributing tests

This file is covered by 3171 tests. .

Source view

1    /*
2    * Copyright 2009 The Closure Compiler Authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10    * Unless required by applicable law or agreed to in writing, software
11    * distributed under the License is distributed on an "AS IS" BASIS,
12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13    * See the License for the specific language governing permissions and
14    * limitations under the License.
15    */
16   
17    package com.google.javascript.jscomp;
18   
19    import com.google.common.base.Preconditions;
20    import com.google.common.collect.LinkedHashMultimap;
21    import com.google.common.collect.Lists;
22    import com.google.common.collect.Maps;
23    import com.google.common.collect.Multimap;
24    import com.google.javascript.jscomp.DefinitionsRemover.Definition;
25    import com.google.javascript.jscomp.DefinitionsRemover.ExternalNameOnlyDefinition;
26    import com.google.javascript.jscomp.DefinitionsRemover.UnknownDefinition;
27    import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
28    import com.google.javascript.rhino.JSDocInfo;
29    import com.google.javascript.rhino.Node;
30    import java.util.Collection;
31    import java.util.List;
32    import java.util.Map;
33   
34    /**
35    * Simple name-based definition gatherer that implements
36    * {@link DefinitionProvider}.
37    *
38    * It treats all variable writes as happening in the global scope and
39    * treats all objects as capable of having the same set of properties.
40    * The current implementation only handles definitions whose right
41    * hand side is an immutable value or function expression. All
42    * complex definitions are treated as unknowns.
43    *
44    */
 
45    class SimpleDefinitionFinder implements CompilerPass, DefinitionProvider {
46    private final AbstractCompiler compiler;
47    private final Map<Node, DefinitionSite> definitionSiteMap;
48    private final Multimap<String, Definition> nameDefinitionMultimap;
49    private final Multimap<String, UseSite> nameUseSiteMultimap;
50   
 
51  6331 toggle public SimpleDefinitionFinder(AbstractCompiler compiler) {
52  6331 this.compiler = compiler;
53  6331 this.definitionSiteMap = Maps.newLinkedHashMap();
54  6331 this.nameDefinitionMultimap = LinkedHashMultimap.create();
55  6331 this.nameUseSiteMultimap = LinkedHashMultimap.create();
56    }
57   
58    /**
59    * Returns the collection of definition sites found during traversal.
60    *
61    * @return definition site collection.
62    */
 
63  1732 toggle public Collection<DefinitionSite> getDefinitionSites() {
64  1732 return definitionSiteMap.values();
65    }
66   
 
67  942 toggle private DefinitionSite getDefinitionAt(Node node) {
68  942 return definitionSiteMap.get(node);
69    }
70   
 
71  942 toggle DefinitionSite getDefinitionForFunction(Node function) {
72  942 Preconditions.checkState(function.isFunction());
73  942 return getDefinitionAt(getNameNodeFromFunctionNode(function));
74    }
75   
 
76  134843 toggle @Override
77    public Collection<Definition> getDefinitionsReferencedAt(Node useSite) {
78  134843 if (definitionSiteMap.containsKey(useSite)) {
79  14940 return null;
80    }
81   
82  119903 if (useSite.isGetProp()) {
83  4427 String propName = useSite.getLastChild().getString();
84  4427 if (propName.equals("apply") || propName.equals("call")) {
85  641 useSite = useSite.getFirstChild();
86    }
87    }
88   
89  119903 String name = getSimplifiedName(useSite);
90  119903 if (name != null) {
91  30351 Collection<Definition> defs = nameDefinitionMultimap.get(name);
92  30351 if (!defs.isEmpty()) {
93  19836 return defs;
94    } else {
95  10515 return null;
96    }
97    } else {
98  89552 return null;
99    }
100    }
101   
 
102  6313 toggle @Override
103    public void process(Node externs, Node source) {
104  6313 NodeTraversal.traverse(
105    compiler, externs, new DefinitionGatheringCallback(true));
106  6313 NodeTraversal.traverse(
107    compiler, source, new DefinitionGatheringCallback(false));
108  6313 NodeTraversal.traverse(
109    compiler, source, new UseSiteGatheringCallback());
110    }
111   
112    /**
113    * Returns a collection of use sites that may refer to provided
114    * definition. Returns an empty collection if the definition is not
115    * used anywhere.
116    *
117    * @param definition Definition of interest.
118    * @return use site collection.
119    */
 
120  4867 toggle Collection<UseSite> getUseSites(Definition definition) {
121  4867 String name = getSimplifiedName(definition.getLValue());
122  4867 return nameUseSiteMultimap.get(name);
123    }
124   
125    /**
126    * Extract a name from a node. In the case of GETPROP nodes,
127    * replace the namespace or object expression with "this" for
128    * simplicity and correctness at the expense of inefficiencies due
129    * to higher chances of name collisions.
130    *
131    * TODO(user) revisit. it would be helpful to at least use fully
132    * qualified names in the case of namespaces. Might not matter as
133    * much if this pass runs after "collapsing properties".
134    */
 
135  188433 toggle private static String getSimplifiedName(Node node) {
136  188433 if (node.isName()) {
137  70166 String name = node.getString();
138  70166 if (name != null && !name.isEmpty()) {
139  67613 return name;
140    } else {
141  2553 return null;
142    }
143  118267 } else if (node.isGetProp()) {
144  22530 return "this." + node.getLastChild().getString();
145    }
146  95737 return null;
147    }
148   
 
149    private class DefinitionGatheringCallback extends AbstractPostOrderCallback {
150    private boolean inExterns;
151   
 
152  12626 toggle DefinitionGatheringCallback(boolean inExterns) {
153  12626 this.inExterns = inExterns;
154    }
155   
 
156  328177 toggle @Override
157    public void visit(NodeTraversal traversal, Node node, Node parent) {
158    // Arguments of external functions should not count as name
159    // definitions. They are placeholder names for documentation
160    // purposes only which are not reachable from anywhere.
161  328177 if (inExterns && node.isName() && parent.isParamList()) {
162  4986 return;
163    }
164   
165  323191 Definition def =
166    DefinitionsRemover.getDefinition(node, inExterns);
167  323191 if (def != null) {
168  35656 String name = getSimplifiedName(def.getLValue());
169  35656 if (name != null) {
170  35286 Node rValue = def.getRValue();
171  35286 if ((rValue != null) &&
172    !NodeUtil.isImmutableValue(rValue) &&
173    !rValue.isFunction()) {
174   
175    // Unhandled complex expression
176  5118 Definition unknownDef =
177    new UnknownDefinition(def.getLValue(), inExterns);
178  5118 def = unknownDef;
179    }
180   
181    // TODO(johnlenz) : remove this stub dropping code if it becomes
182    // illegal to have untyped stubs in the externs definitions.
183  35286 if (inExterns) {
184    // We need special handling of untyped externs stubs here:
185    // the stub should be dropped if the name is provided elsewhere.
186   
187  20478 List<Definition> stubsToRemove = Lists.newArrayList();
188  20478 String qualifiedName = node.getQualifiedName();
189   
190    // If there is no qualified name for this, then there will be
191    // no stubs to remove. This will happen if node is an object
192    // literal key.
193  20478 if (qualifiedName != null) {
194  20474 for (Definition prevDef : nameDefinitionMultimap.get(name)) {
195  1641 if (prevDef instanceof ExternalNameOnlyDefinition
196    && !jsdocContainsDeclarations(node)) {
197  810 String prevName = prevDef.getLValue().getQualifiedName();
198  810 if (qualifiedName.equals(prevName)) {
199    // Drop this stub, there is a real definition.
200  540 stubsToRemove.add(prevDef);
201    }
202    }
203    }
204   
205  20474 for (Definition prevDef : stubsToRemove) {
206  540 nameDefinitionMultimap.remove(name, prevDef);
207    }
208    }
209    }
210   
211  35286 nameDefinitionMultimap.put(name, def);
212  35286 definitionSiteMap.put(node,
213    new DefinitionSite(node,
214    def,
215    traversal.getModule(),
216    traversal.inGlobalScope(),
217    inExterns));
218    }
219    }
220   
221  323191 if (inExterns && (parent != null) && parent.isExprResult()) {
222  14757 String name = getSimplifiedName(node);
223  14757 if (name != null) {
224   
225    // TODO(johnlenz) : remove this code if it becomes illegal to have
226    // stubs in the externs definitions.
227   
228    // We need special handling of untyped externs stubs here:
229    // the stub should be dropped if the name is provided elsewhere.
230    // We can't just drop the stub now as it needs to be used as the
231    // externs definition if no other definition is provided.
232   
233  6463 boolean dropStub = false;
234  6463 if (!jsdocContainsDeclarations(node)) {
235  3847 String qualifiedName = node.getQualifiedName();
236  3847 if (qualifiedName != null) {
237  3845 for (Definition prevDef : nameDefinitionMultimap.get(name)) {
238  540 String prevName = prevDef.getLValue().getQualifiedName();
239  540 if (qualifiedName.equals(prevName)) {
240  540 dropStub = true;
241  540 break;
242    }
243    }
244    }
245    }
246   
247  6463 if (!dropStub) {
248    // Incomplete definition
249  5923 Definition definition = new ExternalNameOnlyDefinition(node);
250  5923 nameDefinitionMultimap.put(name, definition);
251  5923 definitionSiteMap.put(node,
252    new DefinitionSite(node,
253    definition,
254    traversal.getModule(),
255    traversal.inGlobalScope(),
256    inExterns));
257    }
258    }
259    }
260    }
261   
262    /**
263    * @return Whether the node has a JSDoc that actually declares something.
264    */
 
265  7273 toggle private boolean jsdocContainsDeclarations(Node node) {
266  7273 JSDocInfo info = node.getJSDocInfo();
267  7273 return (info != null && info.containsDeclaration());
268    }
269    }
270   
 
271    private class UseSiteGatheringCallback extends AbstractPostOrderCallback {
 
272  124018 toggle @Override
273    public void visit(NodeTraversal traversal, Node node, Node parent) {
274   
275  124018 Collection<Definition> defs = getDefinitionsReferencedAt(node);
276  124018 if (defs == null) {
277  110863 return;
278    }
279   
280  13155 Definition first = defs.iterator().next();
281   
282  13155 String name = getSimplifiedName(first.getLValue());
283  13155 Preconditions.checkNotNull(name);
284  13155 nameUseSiteMultimap.put(
285    name,
286    new UseSite(node, traversal.getScope(), traversal.getModule()));
287    }
288    }
289   
290    /**
291    * @param use A use site to check.
292    * @return Whether the use is a call or new.
293    */
 
294  3222 toggle static boolean isCallOrNewSite(UseSite use) {
295  3222 Node call = use.node.getParent();
296  3222 if (call == null) {
297    // The node has been removed from the AST.
298  0 return false;
299    }
300    // We need to make sure we're dealing with a call to the function we're
301    // optimizing. If the the first child of the parent is not the site, this
302    // is a nested call and it's a call to another function.
303  3222 return NodeUtil.isCallOrNew(call) && call.getFirstChild() == use.node;
304    }
305   
 
306  1249 toggle boolean canModifyDefinition(Definition definition) {
307  1249 if (isExported(definition)) {
308  6 return false;
309    }
310   
311    // Don't modify unused definitions for two reasons:
312    // 1) It causes unnecessary churn
313    // 2) Other definitions might be used to reflect on this one using
314    // goog.reflect.object (the check for definitions with uses is below).
315  1243 Collection<UseSite> useSites = getUseSites(definition);
316  1243 if (useSites.isEmpty()) {
317  92 return false;
318    }
319   
320  1151 for (UseSite site : useSites) {
321    // This catches the case where an object literal in goog.reflect.object
322    // and a prototype method have the same property name.
323   
324    // NOTE(nicksantos): Maps and trogedit both do this by different
325    // mechanisms.
326   
327  1803 Node nameNode = site.node;
328  1803 Collection<Definition> singleSiteDefinitions =
329    getDefinitionsReferencedAt(nameNode);
330  1803 if (singleSiteDefinitions.size() > 1) {
331  60 return false;
332    }
333   
334  1743 Preconditions.checkState(!singleSiteDefinitions.isEmpty());
335  1743 Preconditions.checkState(singleSiteDefinitions.contains(definition));
336    }
337   
338  1091 return true;
339    }
340   
341    /**
342    * @return Whether the definition is directly exported.
343    */
 
344  1249 toggle private boolean isExported(Definition definition) {
345    // Assume an exported method result is used.
346  1249 Node lValue = definition.getLValue();
347  1249 if (lValue == null) {
348  0 return true;
349    }
350   
351  1249 String partialName;
352  1249 if (lValue.isGetProp()) {
353  146 partialName = lValue.getLastChild().getString();
354  1103 } else if (lValue.isName()) {
355  1103 partialName = lValue.getString();
356    } else {
357    // GETELEM is assumed to be an export or other expression are unknown
358    // uses.
359  0 return true;
360    }
361   
362  1249 CodingConvention codingConvention = compiler.getCodingConvention();
363  1249 if (codingConvention.isExported(partialName)) {
364  6 return true;
365    }
366   
367  1243 return false;
368    }
369   
370    /**
371    * @return Whether the function is defined in a non-aliasing expression.
372    */
 
373  1419 toggle static boolean isSimpleFunctionDeclaration(Node fn) {
374  1419 Node parent = fn.getParent();
375  1419 Node gramps = parent.getParent();
376   
377    // Simple definition finder doesn't provide useful results in some
378    // cases, specifically:
379    // - functions with recursive definitions
380    // - functions defined in object literals
381    // - functions defined in array literals
382    // Here we defined a set of known function declaration that are 'ok'.
383   
384    // Some projects seem to actually define "JSCompiler_renameProperty"
385    // rather than simply having an extern definition. Don't mess with it.
386  1419 Node nameNode = SimpleDefinitionFinder.getNameNodeFromFunctionNode(fn);
387  1419 if (nameNode != null
388    && nameNode.isName()) {
389  1179 String name = nameNode.getString();
390  1179 if (name.equals(NodeUtil.JSC_PROPERTY_NAME_FN) ||
391    name.equals(
392    ObjectPropertyStringPreprocess.EXTERN_OBJECT_PROPERTY_STRING)) {
393  16 return false;
394    }
395    }
396   
397    // example: function a(){};
398  1403 if (NodeUtil.isFunctionDeclaration(fn)) {
399  689 return true;
400    }
401   
402    // example: a = function(){};
403    // example: var a = function(){};
404  714 if (fn.getFirstChild().getString().isEmpty()
405    && (NodeUtil.isExprAssign(gramps) || parent.isName())) {
406  560 return true;
407    }
408   
409  154 return false;
410    }
411   
412    /**
413    * @return the node defining the name for this function (if any).
414    */
 
415  2361 toggle static Node getNameNodeFromFunctionNode(Node function) {
416  2361 Preconditions.checkState(function.isFunction());
417  2361 if (NodeUtil.isFunctionDeclaration(function)) {
418  1002 return function.getFirstChild();
419    } else {
420  1359 Node parent = function.getParent();
421  1359 if (NodeUtil.isVarDeclaration(parent)) {
422  1008 return parent;
423  351 } else if (parent.isAssign()) {
424  238 return parent.getFirstChild();
425  113 } else if (NodeUtil.isObjectLitKey(parent, parent.getParent())) {
426  30 return parent;
427    }
428    }
429  83 return null;
430    }
431   
432    /**
433    * Traverse a node and its children and remove any references to from
434    * the structures.
435    */
 
436  95 toggle void removeReferences(Node node) {
437  95 if (DefinitionsRemover.isDefinitionNode(node)) {
438  4 DefinitionSite defSite = definitionSiteMap.get(node);
439  4 if (defSite != null) {
440  4 Definition def = defSite.definition;
441  4 String name = getSimplifiedName(def.getLValue());
442  4 if (name != null) {
443  4 this.definitionSiteMap.remove(node);
444  4 this.nameDefinitionMultimap.remove(name, node);
445    }
446    }
447    } else {
448  91 Node useSite = node;
449  91 if (useSite.isGetProp()) {
450  0 String propName = useSite.getLastChild().getString();
451  0 if (propName.equals("apply") || propName.equals("call")) {
452  0 useSite = useSite.getFirstChild();
453    }
454    }
455  91 String name = getSimplifiedName(useSite);
456  91 if (name != null) {
457  17 this.nameUseSiteMultimap.remove(name, new UseSite(useSite, null, null));
458    }
459    }
460   
461  95 for (Node child : node.children()) {
462  20 removeReferences(child);
463    }
464    }
465    }